using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TimeWar.Octree;
using Microsoft.Xna.Framework;

namespace TimeWar
{
    public class ParticleOctreeNode : ITimeWarObject
    {

        protected internal ParticleOctreeNode[] branch;
        protected internal List<ParticleOctreeLeaf> items;
        protected internal bool allTheSamePoint;
        protected internal Vector3 first;
        protected internal int maxItems;
        protected internal float minSize;
        public OctreeBox bounds;

        public const float NO_MIN_SIZE = -1;
        public const float DEFAULT_MIN_SIZE = 5;

        protected Vector3 vector;
        protected float radius;

        protected BoundingFrustum m_frustum = null;
        protected Matrix m_view;
        protected Matrix m_projection;

        public void setRenderParameters(BoundingFrustum frustum, Matrix view, Matrix projection)
        {
            m_frustum = frustum;
            m_projection = projection;
            m_view = view;
        }

        public void setRange(Vector3 v, float r)
        {
            vector = v;
            radius = r;
        }



        public ParticleOctreeNode(float xMax, float xMin, float yMax, float yMin, float zMax, float zMin, int maximumItems)
            : this(xMax, xMin, yMax, yMin, zMax, zMin, maximumItems, NO_MIN_SIZE)
        {
        }

        public ParticleOctreeNode(float xMax, float xMin, float yMax, float yMin, float zMax, float zMin, int maximumItems, float minimumSize)
        {
            bounds = new OctreeBox(xMax, xMin, yMax, yMin, zMax, zMin);
            maxItems = maximumItems;
            minSize = minimumSize;
            //items = ArrayList.Synchronized(new ArrayList(10));
            items = new List<ParticleOctreeLeaf>();
        }


        public Boolean AddNode(ParticleOctreeLeaf leaf)
        {
            if (branch == null)
            {
                this.items.Add(leaf);
                if (this.items.Count == 1)
                {
                    this.allTheSamePoint = true;
                    this.first.X = leaf.X;
                    this.first.Y = leaf.Y;
                    this.first.Z = leaf.Z;
                }
                else
                {
                    if (this.first.X != leaf.X || this.first.Y != leaf.Y || this.first.Z != leaf.Z)
                    {
                        this.allTheSamePoint = false;
                    }
                }

                if (this.items.Count > maxItems && !this.allTheSamePoint)
                    split();
                return true;
            }
            else
            {
                ParticleOctreeNode node = getChild(leaf.Vector);
                if (node != null)
                {
                    return node.AddNode(leaf);
                }
            }
            return false;
        }

        protected internal void split()
        {
            // Make sure we're bigger than the minimum, if we care,
            if (minSize != NO_MIN_SIZE)
                if (Math.Abs(bounds.Top - bounds.Bottom) < minSize &&
                    Math.Abs(bounds.Right - bounds.Left) < minSize &&
                    Math.Abs(bounds.Front - bounds.Back) < minSize)
                    return;

            float nsHalf = (float)(bounds.Top - (bounds.Top - bounds.Bottom) * 0.5);
            float ewHalf = (float)(bounds.Right - (bounds.Right - bounds.Left) * 0.5);
            float fbHalf = (float)(bounds.Front - (bounds.Front - bounds.Back) * 0.5);

            branch = new ParticleOctreeNode[8];

            branch[0] = new ParticleOctreeNode(ewHalf, bounds.Left, bounds.Front, fbHalf, bounds.Top, nsHalf, maxItems); //left-front-top
            branch[1] = new ParticleOctreeNode(bounds.Right, ewHalf, bounds.Front, fbHalf, bounds.Top, nsHalf, maxItems);
            branch[2] = new ParticleOctreeNode(ewHalf, bounds.Left, bounds.Front, fbHalf, nsHalf, bounds.Bottom, maxItems);
            branch[3] = new ParticleOctreeNode(bounds.Right, ewHalf, bounds.Front, fbHalf, nsHalf, bounds.Bottom, maxItems);

            branch[4] = new ParticleOctreeNode(ewHalf, bounds.Left, fbHalf, bounds.Back, bounds.Top, nsHalf, maxItems); //left-back-top
            branch[5] = new ParticleOctreeNode(bounds.Right, ewHalf, fbHalf, bounds.Back, bounds.Top, nsHalf, maxItems);
            branch[6] = new ParticleOctreeNode(ewHalf, bounds.Left, fbHalf, bounds.Back, nsHalf, bounds.Bottom, maxItems);
            branch[7] = new ParticleOctreeNode(bounds.Right, ewHalf, fbHalf, bounds.Back, nsHalf, bounds.Bottom, maxItems);

            //ArrayList temp = (ArrayList)items.Clone();
            List<ParticleOctreeLeaf> temp = items;
            items.Clear();
            IEnumerator<ParticleOctreeLeaf> things = temp.GetEnumerator();
            while (things.MoveNext())
            {
                AddNode((ParticleOctreeLeaf)things.Current);
            }
        }


        public ParticleSystem RemoveNode(ParticleSystem obj)
        {
            ParticleOctreeLeaf m_leaf = new ParticleOctreeLeaf(obj);

            if (branch == null)
            {
                // This must be the node that has it...
                for (int i = 0; i < items.Count; i++)
                {
                    ParticleOctreeLeaf qtl = (ParticleOctreeLeaf)items[i];
                    if (m_leaf.Value.Equals(qtl.Value))
                    {
                        items.RemoveAt(i);
                        return qtl.Value;
                    }
                }
            }
            else
            {
                ParticleOctreeNode node = getChild(m_leaf.Vector);
                if (node != null)
                {
                    return node.RemoveNode(m_leaf.Value);
                }
            }
            return null;
        }

        public void Clear()
        {
            this.items.Clear();
            if (branch != null)
            {
                for (int i = 0; i < branch.Length; i++)
                {
                    branch[i].Clear();
                }
                branch = null;
            }
        }

        protected internal ParticleOctreeNode getChild(Vector3 vector)
        {
            Double x = Convert.ToDouble(vector.X);
            Double y = Convert.ToDouble(vector.Y);
            Double z = Convert.ToDouble(vector.Z);

            if (bounds.pointWithinBounds(x, y, z))
            {
                if (branch != null)
                {
                    for (int i = 0; i < branch.Length; i++)
                        if (branch[i].bounds.pointWithinBounds(x, y, z))
                            return branch[i].getChild(vector);

                }
                else
                    return this;
            }
            return null;
        }

        public GameState Update(TimeSpan time, TimeSpan elapsedTime)
        {
            double distance;
            if (branch == null)
            {
                foreach (ParticleOctreeLeaf leaf in this.items)
                {
                    
                    distance = Math.Sqrt(
                                Math.Pow(vector.X - leaf.X, 2.0) +
                                Math.Pow(vector.Y - leaf.Y, 2.0) +
                                Math.Pow(vector.Z - leaf.Z, 2.0));

                    if (distance < radius)
                    {
                        return leaf.Update(time, elapsedTime);
                    }

                }
            }
            else
            {
                // Check the distance of the bounds of the branch,
                // versus the bestDistance. If there is a boundary that
                // is closer, then it is possible that another node has an
                // object that is closer.
                for (int i = 0; i < branch.Length; i++)
                {
                    double childDistance = branch[i].bounds.borderDistance(vector.X, vector.Y, vector.Z);

                    if (childDistance < radius)
                    {
                        ParticleSystem test = branch[i].GetNode(vector, radius);
                        if (test != null)
                        {
                            return test.Update(time, elapsedTime);
                        }
                    }

                }
            }

            return GameState.None;

        }


        public void Render()
        {
            double distance;
            if (branch == null)
            {
                foreach (ParticleOctreeLeaf leaf in this.items)
                {
                    distance = Math.Sqrt(
                                Math.Pow(vector.X - leaf.X, 2.0) +
                                Math.Pow(vector.Y - leaf.Y, 2.0) +
                                Math.Pow(vector.Z - leaf.Z, 2.0));

                    if (distance < radius)
                    {
                        for (int i = 0; i < leaf.Value.TotalParticles; i++)
                        {
                            if (m_frustum.Intersects(leaf.Value.GetBoundingBoxTransformed(i)))
                            {
                                leaf.setCamera(m_view, m_projection);
                                leaf.Render();
                            }
                        }
                    }

                }
            }
            else
            {
                // Check the distance of the bounds of the branch,
                // versus the bestDistance. If there is a boundary that
                // is closer, then it is possible that another node has an
                // object that is closer.
                for (int i = 0; i < branch.Length; i++)
                {
                    double childDistance = branch[i].bounds.borderDistance(vector.X, vector.Y, vector.Z);

                    if (childDistance < radius)
                    {
                        ParticleSystem test = branch[i].GetNode(vector, radius);
                        if (test != null)
                        {
                            for (int j = 0; j < test.TotalParticles; j++)
                            {
                                if (m_frustum.Intersects(test.GetBoundingBoxTransformed(j)))
                                {
                                    test.SetCamera(m_view, m_projection);
                                    test.Render();
                                }
                            }
                        }
                    }

                }
            }
        }

        public ParticleSystem GetNode(Vector3 vector, double ShortestDistance)
        {
            double distance;
            ParticleSystem closest = null;
            if (branch == null)
            {
                foreach (ParticleOctreeLeaf leaf in this.items)
                {
                    distance = Math.Sqrt(
                                Math.Pow(vector.X - leaf.X, 2.0) +
                                Math.Pow(vector.Y - leaf.Y, 2.0) +
                                Math.Pow(vector.Z - leaf.Z, 2.0));

                    if (distance < ShortestDistance)
                    {
                        ShortestDistance = distance;
                        closest = leaf.Value;
                    }
                }
                return closest;
            }
            else
            {
                // Check the distance of the bounds of the branch,
                // versus the bestDistance. If there is a boundary that
                // is closer, then it is possible that another node has an
                // object that is closer.
                for (int i = 0; i < branch.Length; i++)
                {
                    double childDistance = branch[i].bounds.borderDistance(vector.X, vector.Y, vector.Z);
                    if (childDistance < ShortestDistance)
                    {
                        ParticleSystem test = branch[i].GetNode(vector, ShortestDistance);
                        if (test != null)
                            closest = test;
                    }
                }
            }
            return closest;
        }


    }
}